/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */
#pragma once

#define CACHE_INVERT_GLOBAL_MATRIX

#include "NodeBase.h"
#include <lib3d/CameraDesc.h>
#include <lib3d/hard/RenderList.h>
#include <filesystem/String.hpp>
#include <WowPtr.h>
#include <NoCopy.h>
#include <list>
#include <lib3d/node/ProxyVisitor.h>
#include <CompilerFeatures.h>

namespace liba {
namespace lib3d {
namespace hard { class Hardware; }
namespace node {

class Root;

class Node 
	: public NodeBase
	, public virtual WowPointable
	, private NoAssign
{
	NODE_DEFINITION(Node)
public:
	typedef std::list<Node *> Children;
	static const Atom node_name;

	//Attributes
	static const Atom visibility_name;
	Atom GetType()const { return node_type; }

	Node( const Atom & name, Node * parent );
	virtual ~Node();

	Node(const Node & other);
	virtual Node * Clone();

	virtual animate::Node * FindAnimateNode( const Atom & name, bool recursion );
	Node * FindNode( const Atom & name, bool recursion );
	template <typename T>
	T* FindNode(const Atom& name, bool recursion)
	{
		Node* n = FindNode(name, recursion);
		if (!n)
			return 0;
		return n->SafeDowncast<T>();
	}

	void Clear();
	virtual void RenderToList( hard::Hardware * hardware, hard::RenderList * render_list );
	Node * GetParent()const;

	virtual Root * GetRoot();
	void AddChild( Node * node, bool change_world_pos = false );
	void RemoveChild( Node * node, bool change_world_pos = false );
	void RemoveFromParent( bool change_world_pos = false );

	const Matrix& GetGlobalMatrix() const;
	void SetGlobalMatrix( const Matrix & mat );

#if defined( CACHE_INVERT_GLOBAL_MATRIX )
	const Matrix& GetInvertGlobalMatrix() const;
#else
	Matrix GetInvertGlobalMatrix() const;
#endif

	void ChangeVisibility( bool visible );
	bool GetVisibility()const;

	using NodeBase::ChangeRotation;
	void ChangeRotation( const Vector & target_pos, const Vector & top );

	Vector GetGlobalPosition()const;
	Vector GetGlobalRotation()const;
	Vector GetGlobalScale()const;

	void ChangeGlobalPosition(const Vector & position);
	void ChangeGlobalRotation(const Vector & rotation);
	void ChangeGlobalScale(const Vector & scale);

	void SetTarget(Node * target, const Vector & top)
	{
		this->target = target;
		top_vector = top;
	}
	
	size_t ChildrenCount() const
	{
		return children.size();
	}
	Node * GetChild(size_t index) const
	{
		if (index >= ChildrenCount())
			return NULL;
		
		Children::const_iterator itr = children.begin();
		for(size_t i = 0; i < index; i++)
			++itr;

		return *itr;
	}
	Children::const_iterator ChildrenBegin()const
	{
		return children.begin();
	}
	Children::const_iterator ChildrenEnd()const
	{
		return children.end();
	}

	// This is partially implemented visitor so if you ever need a node visitor 
	// consider upgrading this rather than implementing your own.
	virtual void CreateProxy(ProxyVisitor * proxyVisitor);
protected:
	virtual void OnLocalMatrixInvalidated() LIBA_OVERRIDE;
	void InvalidateGlobalMatrix();
	virtual void OnGlobalMatrixInvalidated() { }
	Matrix GetRotationMatrix() const;

	virtual bool on_attribute( xml::Provider * prov, const std::string & name, const std::string & value ) LIBA_OVERRIDE;
	virtual bool on_node( xml::Provider * prov, const std::string & name ) LIBA_OVERRIDE;

	virtual void on_save_attributes( xml::SaverBase * saver )const LIBA_OVERRIDE;
	virtual void on_save_nodes( xml::SaverBase * saver )const LIBA_OVERRIDE;

	mutable Matrix globalMatrix;
	mutable bool globalMatrixDirty;

#if defined( CACHE_INVERT_GLOBAL_MATRIX )
	mutable Matrix invertGlobalMatrix;
	mutable bool invertGlobalMatrixDirty;
#endif

	properties::DependencyProperty<bool> visibility;

	Vector top_vector;
	std::string target_name;
	WowPtr<Node> target;

	Node * parent;
	Children children;
	Children::iterator pos_in_parent;

	std::string children_scene_name;
	mutable bool ignore_children_scene_name;

	Atom node_type;
};


inline Node * Node::GetParent()const
{
	return parent;
}

inline Root * Node::GetRoot()
{
	return parent->GetRoot();
}

inline void Node::ChangeVisibility( bool visible )
{
	if(visible != visibility)
		visibility = visible;
}

inline bool Node::GetVisibility()const
{
	return visibility;
}

Node * CreateFromResource( const Atom & name, Node * parent, const filesystem::String & resource_name );

} // namespace node
} // namespace lib3d
} // namespace liba
using namespace liba;
